<?php
/**
 * 经纬度与UTM坐标转换
 */

class Map {

    /**
     * 构造函数
     *
     * @access public
     */
    function __construct() {
    	// do nothing.
    }

    /**
     * UTM=>Latitude,Longitude(纬度、经度)
     *
     * @param string utm
     *
     * @return double
     */
    public function utm2LatLon($utm='') {
    	$c = new UTM2LatLon();
    	return $c->convertUTMToLatLong($utm);
    }

    /**
     * Latitude,Longitude(纬度、经度)=>UTM
     *
     * @param double latitude
     * @param double longitude
     *
     * @return string
     */
    public function latLng2UTM($latitude, $longitude) {

    	$c = new LatLng2UTM();

    	return $c->convertLatLngToUTM($latitude, $longitude);
    }

    /**
     * 把经纬度转化为对应的像素坐标
     *
     * 适用于UVAN 6000*4500地图
     *
     * @param double latitude
     * @param double longitude
     *
     * @return array
     */
    public function latLng2Pixel($latitude, $longitude) {

    	$c = new LatLng2UTM();

    	return $c->convertLatLngToPixel($latitude, $longitude);
    }

    /**
     * 把UVAN像素坐标转化为经纬度
     *
     * @param int x
     * @param int y
     *
     * @return array array('lat'=>..., 'lng'=>...)
     */
    public function pixel2LatLng($x, $y) {

    	$c = new LatLng2UTM();

    	return $c->convertPixelToLatLng($x, $y);
    }
}

class LatLng2UTM {

	// Lat Lon to UTM variables

    // equatorial radius, type double
    private $equatorialRadius = 6378137;

    // polar radius, type double
    private $polarRadius = 6356752.314;

    // flattening, type double
    private $flattening = 0.00335281066474748; // (equatorialRadius-polarRadius)/equatorialRadius;

    // inverse flattening 1/flattening, type double
    private $inverseFlattening = 298.257223563;  // 1/flattening;

    // Mean radius, type double
    private $rm; // = pow($equatorialRadius * polarRadius, 1 / 2.0);

    // scale factor, type double
    private $k0 = 0.9996;

    // eccentricity, type double
    private $e; // = sqrt(1 - pow(polarRadius / equatorialRadius, 2));

    private $e1sq; // = e * e / (1 - e * e); // type double

    private $n; // = (equatorialRadius - polarRadius) / (equatorialRadius + polarRadius);

    // r curv 1, type double
    private $rho = 6368573.744;

    // r curv 2, type double
    private $nu = 6389236.914;

    // Calculate Meridional Arc Length
    // Meridional Arc, type double
    private $S = 5103266.421;

    private $A0 = 6367449.146;  // type double

    private $B0 = 16038.42955;  // type double

    private $C0 = 16.83261333;  // type double

    private $D0 = 0.021984404;  // type double

    private $E0 = 0.000312705;  // type double

    // Calculation Constants
    // Delta Long, type double
    private $p = -0.483084;

    private $sin1 = 4.84814E-06;  // type double

    // Coefficients for UTM Coordinates, type double
    private $K1 = 5101225.115;

    private $K2 = 3750.291596;  // type double

    private $K3 = 1.397608151;  // type double

    private $K4 = 214839.3105;  // type double

    private $K5 = -2.995382942;  // type double

    private $A6 = -1.00541E-07;  // type double

    /* 定义UVAN 6000*4500地图的经纬度 */
    /* left_p1====================right_p1 */
    /*      ||                    ||       */
    /*      ||                    ||       */
    /* left_p2====================right_p2 */
    /* left_p3====================right_p3 */
    /*      ||                    ||       */
    /*      ||                    ||       */
    /* left_p4====================right_p4 */
    private $left_p1   = array('lat'=>54.74999,  'lng'=>71.36719);
    private $right_p1  = array('lat'=>54.74999,  'lng'=>137.21924);

    private $left_p2   = array('lat'=>48.00000,  'lng'=>71.36719);
    private $right_p2  = array('lat'=>48.00000,  'lng'=>137.21924);

    private $left_p3   = array('lat'=>47.99999,  'lng'=>71.36719);
    private $right_p3  = array('lat'=>47.99999,  'lng'=>137.21924);

    private $left_p4   = array('lat'=>16.14082,  'lng'=>71.36719);
    private $right_p4  = array('lat'=>16.14082,  'lng'=>137.21924);

    /**
     * 构造函数
     *
     * @access public
     */
    function __construct() {

    	$this->rm = pow($this->equatorialRadius * $this->polarRadius, 1 / 2.0);

    	$this->e = sqrt(1 - pow($this->polarRadius / $this->equatorialRadius, 2));

    	$this->e1sq = $this->e * $this->e / (1 - $this->e * $this->e);

    	$this->n = ($this->equatorialRadius - $this->polarRadius) / ($this->equatorialRadius + $this->polarRadius);
    }

	/**
     * 检查合法性
     *
     * @param double latitude 纬度
     * @param double longitude 经度
     *
     * @return void
     */
    private function validate($latitude, $longitude) {

    	if ($latitude < -90.0 || $latitude > 90.0 || $longitude < -180.0
        || $longitude >= 180.0) {
        	return false;
        }

        return true;
    }

    /**
     * @param double degree
     *
     * @return double
     */
    public function degreeToRadian($degree) {
    	$pi = pi();
    	return ($degree * $pi / 180);
    }

    /**
     * radian(弧度)=>degree(角度)
     *
     * @param double radian
     *
     * @return double
     */
    public function radianToDegree($radian) {
    	$pi = pi();
    	return ($radian * 180 / $pi);
    }

    /**
     * 把UVAN像素坐标转化为经纬度
     *
     * @param int x
     * @param int y
     *
     * @return array array('lat'=>..., 'lng'=>...)
     */
    public function convertPixelToLatLng($x, $y) {

    	$lp1  = $this->gLatLngToPixel($this->left_p1['lat'],  $this->left_p1['lng']);
    	$lp2  = $this->gLatLngToPixel($this->left_p2['lat'],  $this->left_p2['lng']);
    	$lp3  = $this->gLatLngToPixel($this->left_p3['lat'],  $this->left_p3['lng']);
    	$lp4  = $this->gLatLngToPixel($this->left_p4['lat'],  $this->left_p4['lng']);

    	$rp1 = $this->gLatLngToPixel($this->right_p1['lat'], $this->right_p1['lng']);
    	$rp2 = $this->gLatLngToPixel($this->right_p2['lat'], $this->right_p2['lng']);
    	$rp3 = $this->gLatLngToPixel($this->right_p3['lat'], $this->right_p3['lng']);
    	$rp4 = $this->gLatLngToPixel($this->right_p4['lat'], $this->right_p4['lng']);

    	$gx = 0; $gy = 0;

    	if ($y <= 990) {
    		$gx = ($x * ($rp1['x']-$lp1['x'])) / 6000 + $lp1['x'];
    		$gy = ($y * ($lp2['y']-$lp1['y'])) / 990 + $lp1['y'];
    	}
    	else {

    		$gx = ($x * ($rp1['x']-$lp1['x'])) / 6000 + $lp1['x'];
    		$gy = (($y - 990) * ($lp4['y']-$lp3['y'])) / 3510 + $lp3['y'];
    	}

    	$ret = array('x'=>round($gx), 'y'=>round($gy));

    	$ret = $this->gPixelToLatLng($ret['x'], $ret['y']);

    	return $ret;
    }

    /**
     * 把经纬度转化为对应的像素坐标
     *
     * 适用于UVAN 6000*4500地图
     *
     * @author wangyihuang
     */
    public function convertLatLngToPixel($latitude, $longitude) {

    	$lp1  = $this->gLatLngToPixel($this->left_p1['lat'],  $this->left_p1['lng']);
    	$lp2  = $this->gLatLngToPixel($this->left_p2['lat'],  $this->left_p2['lng']);
    	$lp3  = $this->gLatLngToPixel($this->left_p3['lat'],  $this->left_p3['lng']);
    	$lp4  = $this->gLatLngToPixel($this->left_p4['lat'],  $this->left_p4['lng']);

    	$rp1 = $this->gLatLngToPixel($this->right_p1['lat'], $this->right_p1['lng']);
    	$rp2 = $this->gLatLngToPixel($this->right_p2['lat'], $this->right_p2['lng']);
    	$rp3 = $this->gLatLngToPixel($this->right_p3['lat'], $this->right_p3['lng']);
    	$rp4 = $this->gLatLngToPixel($this->right_p4['lat'], $this->right_p4['lng']);

    	$dot       = $this->gLatLngToPixel($latitude, $longitude);
    	// $dot_left  = $this->gLatLngToPixel($latitude, $this->left_p1['lng']);
    	// $dot_right = $this->gLatLngToPixel($latitude, $this->right_p1['lng']);

    	/// 如果纬度是$left_p1 - $left_p2之间
    	if ($latitude >= $this->left_p2['lat']) {

    		$x = (6000*($dot['x']-$lp1['x']))/($rp1['x']-$lp1['x']);
    		$y = (990*($dot['y']-$lp1['y']))/($lp2['y']-$lp1['y']);

    		return array('x'=>round($x), 'y'=>round($y));
    	}
    	/// 如果纬度是$left_p3 - $left_p4之间
    	else {

    		$x = (6000*($dot['x']-$lp1['x']))/($rp1['x']-$lp1['x']);
    		$y = 990+(3510*($dot['y']-$lp3['y']))/($lp4['y']-$lp3['y']);

    		$ret = array('x'=>round($x), 'y'=>round($y));
    		if ($ret['y'] == 990) $ret['y'] = 989; // 避免上下边界点相等

    		return $ret;
    	}
    }

    /**
     * 把Google地图的像素坐标=>经纬度
     */
    private function gPixelToLatLng($x, $y, $zoom=7) {

		$centerPoint = pow(2, $zoom+7);
		$totalPixels = $centerPoint*2;
		$pixelsPerLngDegree = $totalPixels/360;
		$pixelsPerLngRadian = $totalPixels/(2*pi());

		/// 计算经度
		$lng = ($x - $centerPoint) / $pixelsPerLngDegree;

		/// 计算纬度
		$e = 2.718281828;
		$siny = (pow($e, 2*($centerPoint-$y) / $pixelsPerLngRadian) - 1) / (1 + pow($e, 2*($centerPoint-$y) / $pixelsPerLngRadian));

		$siny = min(max($siny, -0.9999), 0.9999);

		/// 根据siny计算出纬度
		$lat = asin($siny)*180/pi();

		return array('lat'=>$lat, 'lng'=>$lng);
    }

    /**
     * Google地图的 经纬度=>像素 坐标转换
     *
     * @param double latitude
     * @param double longitude
     * @param int zoom
     *
     * @return array array(x=>..., y=>...)
     */
    private function gLatLngToPixel($latitude, $longitude, $zoom=7) {

    	$centerPoint = pow(2, $zoom+7);
		$totalPixels = $centerPoint*2;
		$pixelsPerLngDegree = $totalPixels/360;
		$pixelsPerLngRadian = $totalPixels/(2*pi());

		$siny = min(max(sin($latitude*(pi()/180)), -0.9999), 0.9999);

		return array(
				'x'=>round($centerPoint+$longitude*$pixelsPerLngDegree),
				'y'=>round($centerPoint-0.5*log((1+$siny)/(1-$siny))*$pixelsPerLngRadian),
			);
    }

	/**
	 * @param double latitude
	 * @param double longitude
	 *
	 * @return string
	 */
	public function convertLatLngToUTM($latitude, $longitude) {

		$valid = $this->validate($latitude, $longitude);
		if ($valid === false) return false;

		$UTM = ''; // type string

		$this->setVariables($latitude, $longitude);

		$longZone = $this->getLongZone($longitude); // type string

		$latZones = new LatZones();
		$latZone = $latZones->getLatZone($latitude); // type string

		$_easting = $this->getEasting(); // type double
		$_northing = $this->getNorthing($latitude); // type double

		$UTM = $longZone . ' ' . $latZone . ' ' . ((int)$_easting) . ' '
          . ((int)$_northing);

        return $UTM;
	}

	/**
	 * @param double latitude 纬度
	 * @param double longitude 经度
	 *
	 * @return void
	 */
	public function setVariables($latitude, $longitude) {

		$latitude = $this->degreeToRadian($latitude);

		$this->rho = $this->equatorialRadius * (1 - $this->e * $this->e) / pow(1 - pow($this->e * sin($latitude), 2), 3 / 2.0);
//		dump($this->rho);
		$this->nu = $this->equatorialRadius / pow(1 - pow($this->e * sin($latitude), 2), (1 / 2.0));

		$var1 = 0.0; // type double

		if ($longitude < 0.0) {
			$var1 = ((int) ((180 + $longitude) / 6.0)) + 1;
		}
		else {
			$var1 = ((int) ($longitude / 6)) + 31;
		}

		$var2 = (6 * $var1) - 183; // double
		$var3 = $longitude - $var2; // double
		$this->p = ($var3 * 3600) / 10000;

		$this->S = $this->A0 * $latitude - $this->B0 * sin(2 * $latitude) + $this->C0 * sin(4 * $latitude) - $this->D0
          * sin(6 * $latitude) + $this->E0 * sin(8 * $latitude);

        $this->K1 = $this->S * $this->k0;
        $this->K2 = $this->nu * sin($latitude) * cos($latitude) * pow($this->sin1, 2) * $this->k0 * (100000000) / 2;

        /// 这里进行高精度的科学计算，可能会有问题
        /// 考验PHP的时候到了
        $this->K3 = ((pow($this->sin1, 4) * $this->nu * sin($latitude) * pow(cos($latitude), 3)) / 24)
          * (5 - pow(tan($latitude), 2) + 9 * $this->e1sq * pow(cos($latitude), 2) + 4
              * pow($this->e1sq, 2) * pow(cos($latitude), 4))
          * $this->k0
          * (10000000000000000);

        $this->K4 = $this->nu * cos($latitude) * $this->sin1 * $this->k0 * 10000;

        $this->K5 = pow($this->sin1 * cos($latitude), 3) * ($this->nu / 6)
          * (1 - pow(tan($latitude), 2) + $this->e1sq * pow(cos($latitude), 2)) * $this->k0
          * 1000000000000;

        $this->A6 = (pow($this->p * $this->sin1, 6) * $this->nu * sin($latitude) * pow(cos($latitude), 5) / 720)
          * (61 - 58 * pow(tan($latitude), 2) + pow(tan($latitude), 4) + 270
              * $this->e1sq * pow(cos($latitude), 2) - 330 * $this->e1sq
              * pow(sin($latitude), 2)) * $this->k0 * (1E+24);
    }

    /**
     * 得到经度区的角度区间
     *
     * @param string val, 形如 '01'或者'23'
     *
     * @return array (6.0, 11.999999)
     *
     * @author wangyihuang
     */
    public function getLongZoneDegree($val) {

    	$val = intval($val);

    	if (($val <= 1) || ($val > 60)) return false;

    	return array(($val-1)*6.0-180.0, ($val-1)*6.0+5.999999999-180.0);
    }

    /**
     * @param double longitude
     *
     * @return string
     */
    public function getLongZone($longitude) {

    	$longZone = 0;

    	if ($longitude < 0.0) {
    		$longZone = ((180.0 + $longitude) / 6) + 1;
    	}
    	else {
    		$longZone = ($longitude / 6) + 31;
    	}

    	$val = (int)$longZone;

    	if (strlen($val) == 1) {
    		$val = '0' . $val;
    	}

    	return $val;
    }

    /**
     * @param double $latitude
     *
     * @return double
     */
    public function getNorthing($latitude) {

    	$northing = $this->K1 + $this->K2 * $this->p * $this->p + $this->K3 * pow($this->p, 4);

    	if ($latitude < 0.0) {
    		$northing = 10000000 + $northing;
    	}

    	return $northing;
    }

    /**
     * @return double
     */
    public function getEasting() {
    	return 500000 + ($this->K4 * $this->p + $this->K5 * pow($this->p, 3));
    }
}

class LatZones {

	private $letters = array('A', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Z');

	private $degrees = array(-90, -80, -72, -64, -56, -48, -40, -32, -24, -16, -8, 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 84);

    private $negLetters = array('A', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K','L', 'M');

    private $negDegrees = array(-90, -80, -72, -64, -56, -48, -40, -32, -24, -16, -8);

    private $posLetters = array('N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W','X', 'Z');

    private $posDegrees = array(0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 84);

    private $arrayLength = 22;

    /**
     * 构造函数
     *
     * @access public
     */
    function __construct() {
    	// do nothing.
    }

    /**
     * @param string letter
     *
     * @return int
     */
    public function getLatZoneDegree($letter) {

    	$ltr = strtoupper(substr($letter, 0, 1));

    	for ($i=0; $i < $this->arrayLength; $i++) {

    		if ($this->letters[$i] = $ltr) {
    			return $this->degrees[$i];
    		}
    	}

    	return -100;
    }

    /**
     * @param double latitude
     *
     * @return string
     */
    public function getLatZone($latitude) {

    	$latIndex = -2;
    	$lat = (int)$latitude;

    	if ($lat >= 0) {

    		$len = count($this->posLetters);

    		for ($i=0; $i < $len; $i++) {
    			if ($lat == $this->posDegrees[$i]) {
    				$latIndex = $i;
    				break;
    			}

    			if ($lat > $this->posDegrees[$i]) {
    				continue;
    			}
    			else {
    				$latIndex = $i - 1;
    				break;
    			}
    		}
    	}

    	else {

    		$len = count($this->negLetters);

    		for ($i=0; $i < $len; $i++) {

    			if ($lat == $this->negDegrees[$i]) {

    				$latIndex = $i;
    				break;
    			}

    			if ($lat < $this->negDegrees[$i]) {
    				$latIndex = $i - 1;
    				break;
    			}
    			else {
    				continue;
    			}
    		}
    	}

    	if ($latIndex == -1) {
    		$latIndex = 0;
    	}
    	if ($lat >= 0) {
    		if ($latIndex == -2) {
    			$latIndex = count($this->posLetters) - 1;
    		}

    		return strval($this->posLetters[$latIndex]);
        }
        else {
        	if ($latIndex == -2) {
        		$latIndex = count($this->negLetters) - 1;
        	}

        	return strval($this->negLetters[$latIndex]);
        }
    }
}
?>